#include <linux/io.h>
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/ctype.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/console.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <mach/hardware.h>
#include <mach/platform.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/pinconf-sunxi.h>
#include <mach/sys_config.h>
#include <mach/gpio.h>
#include <mach/irqs.h>
#include <asm/io.h>
#include "ch438.h"

#define CH438_UART_NAME		"usart"
#define CH438_DEV_NAME		"ttySC"
#define CH438_DEV_NUM		8

#define CH438_CLOCK_RATE    (22118400/12)

/* debug control */
enum {
	DBG_ERR  = 1U << 0,
	DBG_DBG  = 1U << 1,
};
static u32 ch438_debug_mask = 0;
#define dprintk(level, fmt, arg...)	\
    do { \
        if (unlikely(ch438_debug_mask & level)) { \
            printk("%s()%d - ", __func__, __LINE__); \
            printk(fmt, ##arg); \
        } \
    } while (0)

#define SERIAL_ERR(fmt, arg...) dprintk(DBG_ERR, fmt, ##arg)
#define SERIAL_DBG(fmt, arg...) dprintk(DBG_DBG, fmt, ##arg)

#define UART_TO_SPORT(port) ((struct ch438_port*)port)

static struct ch438_pin     ch438_pin;
static spinlock_t ch438_rwlock;
static unsigned long ch438_mapbase=0; 
static unsigned char __iomem *ch438_membase=NULL; /*read/write[bwl]*/

static const u8 REG[] = {0x00,0x10,0x20,0x30,0x08,0x18,0x28,0x38};

/* NO CONFIG_SERIAL_SUNXI_CONSOLE, this device don't as console! */

static struct platform_device ch438_device[CH438_DEV_NUM];
static struct ch438_port      ch438_ports[CH438_DEV_NUM];

/* Access macros for the CH438 UART */

#define UART_GET_CHR(p)		RdReg(REG[p->line]|RBR_RO)
#define UART_PUT_CHR(p, c)	WrReg(REG[p->line]|THR_WO, c)

#define UART_GET_IER(p)		RdReg(REG[p->line]|IER_RW)
#define UART_PUT_IER(p, c)	WrReg(REG[p->line]|IER_RW, c)
#define UART_GET_IIR(p)		RdReg(REG[p->line]|IIR_RO)

#define UART_GET_FCR(p)		RdReg(REG[p->line]|FCR_WO)
#define UART_PUT_FCR(p, c)	WrReg(REG[p->line]|FCR_WO, c)
#define UART_GET_MSR(p)		RdReg(REG[p->line]|MSR_RO)
#define UART_GET_LSR(p)		RdReg(REG[p->line]|LSR_RO)
#define UART_GET_LCR(p)		RdReg(REG[p->line]|LCR_RW)
#define UART_PUT_LCR(p, c)	WrReg(REG[p->line]|LCR_RW, c)
#define UART_GET_MCR(p)		RdReg(REG[p->line]|MCR_RW)
#define UART_PUT_MCR(p, c)	WrReg(REG[p->line]|MCR_RW, c)
#define UART_GET_SCR(p)		RdReg(REG[p->line]|SCR_RW)
#define UART_PUT_SCR(p, c)	WrReg(REG[p->line]|SCR_RW, c)

#define UART_PUT_DLH(p, c)	WrReg(REG[p->line]|DLH_RW, c)
#define UART_PUT_DLL(p, c)	WrReg(REG[p->line]|DLL_RW, c)

#define UART_GET_SSR()		RdReg(SSR_RO)

static void ch438_gpio_init(void)
{
	struct ch438_pin *pin = &ch438_pin;
    script_item_u val;
    char *para = "ch438"; /* see as sys_config.fex */
	int ret=0;
	int reg = 0;

	SERIAL_DBG("ch438 gpio init.\n");

    __raw_writel(0x55555555, CH438_PULL0); /* all pull up */
    __raw_writel(0x00000005, CH438_PULL1); /* all pull up */
    __raw_writel(0x11111111, CH438_CFG0);  /* PE7~PE0: output */

    __raw_writel(0x11111111, CH438_CFG1);  /* WR[15] ADDR[14~8] output */
    __raw_writel(0x00000011, CH438_CFG2);  /* CS[17], RD[16] */

	/* ch438 RST: PF01 */
    script_get_item(para, "ch438_rst", &val);
	pin->rst = val.gpio.gpio;
	ret=gpio_request(pin->rst, NULL);
	gpio_direction_output(pin->rst, 1);

	/* ch438 INT: PB06 */
    script_get_item(para, "ch438_int", &val);
	pin->pint = val.gpio.gpio;
	ret=gpio_request(pin->pint, NULL);
	gpio_direction_input(pin->pint);

	/* Allocate the IRQ */
	pin->irqnum = gpio_to_irq(pin->pint);

	/* hw reset ch438 */
	SERIAL_DBG("ch438 chip hw reset!\n");
	gpio_set_value(pin->rst, 1);
	udelay(10);
	gpio_set_value(pin->rst, 0);
	udelay(100);
	gpio_set_value(pin->rst, 1);

    reg = __raw_readl(CH438_DAT);
    /* Reset default: CS=1, WR=1, RD=1 */
    __raw_writel(reg|(1<<CH438_CS)|(1<<CH438_WR)|(1<<CH438_RD), CH438_DAT);	
}

static void ch438_gpio_free(void)
{
	struct ch438_pin *pin = &ch438_pin;

	gpio_free(pin->rst);
	gpio_free(pin->pint);
}

static void WrReg(unsigned char addr,unsigned char dat)
{
    unsigned long flags;
	int reg;

    spin_lock_irqsave(&ch438_rwlock, flags);

    /* addr output */
    __raw_writel(0x11111111, CH438_CFG0);
	ndelay(20);
    /* CS=0, RD=1, WR=0, 00... */
    reg = 0x00010000;
    reg |= (unsigned int)addr<<8;
    /* data port */
    reg |= dat;
    __raw_writel(reg, CH438_DAT);
	ndelay(20);
    /* set CS,WR as 0 */
    reg &= ~((1<<CH438_CS)|(1<<CH438_WR));
    __raw_writel(reg, CH438_DAT); 
	ndelay(20);
    //set CS,WR as 1
    reg |= (1<<CH438_CS)|(1<<CH438_WR);
    __raw_writel(reg, CH438_DAT);
	ndelay(20);

    spin_unlock_irqrestore(&ch438_rwlock, flags);
}
 
static unsigned char RdReg(unsigned char addr)
{
    unsigned long flags;
    unsigned char value=0;
	int reg;

    spin_lock_irqsave(&ch438_rwlock, flags);

    /* addr input */
    __raw_writel(0x00000000, CH438_CFG0);
	ndelay(20);
    /* CS=1,WR=1,RD=1, addr */
    reg = 0x00038000 | (unsigned int)addr<<8; 
    __raw_writel(reg, CH438_DAT);
	ndelay(20);
    /* CS=0,RD=0 */
    reg &= ~((1<<CH438_CS)|(1<<CH438_RD));
    __raw_writel(reg, CH438_DAT);
	ndelay(20);
    /* read value */
    value = __raw_readl(CH438_DAT)&0xFF; 
    /* RD=1,CS=1 */
    reg |= (1<<CH438_CS)|(1<<CH438_RD); 
    __raw_writel(reg, CH438_DAT);  
	ndelay(20);

    spin_unlock_irqrestore(&ch438_rwlock, flags);

	return value;
}

static inline void ch438_reset(struct uart_port *port)
{
	UART_PUT_IER(port, BIT_IER_RESET);
}

static unsigned int ch438_handle_rx(struct uart_port *port, unsigned char lsr)
{
	struct tty_struct *tty = port->state->port.tty;
	unsigned char ch = 0;
	int max_count = 256;
	char flag;
	/* unsigned int cnt=0; */
    
    /* printk("ttySC%d RX: ", port->line); */
    /* cnt = port->icount.rx; */
	do {
        if(likely(lsr & BIT_LSR_DR)) {
		    ch = UART_GET_CHR(port);
            /* printk("%02x ", ch); */
        }

		flag = TTY_NORMAL;
		port->icount.rx++;

		if(unlikely(lsr & LSR_BRK_ERROR_BITS)) {
            /* For statistics only */
			if(lsr & BIT_LSR_BI) {
				lsr &= ~(BIT_LSR_FE | BIT_LSR_PE);
				port->icount.brk++;
				/*
				 * We do the SysRQ and SAK checking
				 * here because otherwise the barek
				 * may be masked by ignore_status_mask
				 * or read_status_mask.
				 */
				if(uart_handle_break(port))
					goto ignore_char;
			} else if(lsr & BIT_LSR_PE)
				port->icount.parity++;
			else if(lsr & BIT_LSR_FE)
				port->icount.frame++;
			if(lsr & BIT_LSR_OE)
				port->icount.overrun++;

			/* Mask off conditions which should be ingored. */
			lsr &= port->read_status_mask;

			if(lsr & BIT_LSR_BI)
				flag = TTY_BREAK;
			else if(lsr & BIT_LSR_PE)
				flag = TTY_PARITY;
			else if(lsr & BIT_LSR_FE)
				flag = TTY_FRAME;
		}

		if(uart_handle_sysrq_char(port, ch))
			goto ignore_char;	
		uart_insert_char(port, lsr, BIT_LSR_OE, ch, flag);
ignore_char:
		lsr = UART_GET_LSR(port);
	} while ((lsr & (BIT_LSR_DR|BIT_LSR_BI)) && (max_count-- > 0));
    /* printk("(%d bytes)\n", port->icount.rx-cnt); */
    spin_unlock(&port->lock);
	tty_flip_buffer_push(tty);
    spin_lock(&port->lock);

    return lsr;
}

static void ch438_stop_tx(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);

	if(up->ier & BIT_IER_THRI) {
		up->ier &= ~BIT_IER_THRI;
		SERIAL_DBG("ttySC%d stop tx, ier 0x%02X\n", port->line, up->ier);
		UART_PUT_IER(port, up->ier);
	}
}

static void ch438_start_tx(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);
	/*
	 * when BIT_IER_THRI(0->1), will be generate "THR empty" interrupt.
	 */
	if(!(up->ier & BIT_IER_THRI)) {
		up->ier |= BIT_IER_THRI;
		SERIAL_DBG("ttySC%d start tx, ier 0x%02X\n", port->line, up->ier);
		UART_PUT_IER(port, up->ier);
	}
}

static void ch438_handle_tx(struct uart_port *port)
{
	struct circ_buf *xmit = &port->state->xmit;
	int count;
	/* unsigned int cnt; */
	
	if(port->x_char) {
		/* Send special char - probebly flow control */
		UART_PUT_CHR(port, port->x_char);
		port->icount.tx++;
		port->x_char = 0;
		return;
	}
	if(uart_circ_empty(xmit) || uart_tx_stopped(port)) {
		ch438_stop_tx(port);
		return;
	}
    
    /* The IRQ is for TX FIFO half-empty */
	count = port->fifosize / 2;
    /* printk("ttySC%d TX:", port->line); */
    /* cnt = port->icount.tx; */
	do {
        /* printk("%02x ", xmit->buf[xmit->tail]); */
		UART_PUT_CHR(port, xmit->buf[xmit->tail]);
		xmit->tail = (xmit->tail+1) & (UART_XMIT_SIZE-1);
		port->icount.tx++;
		if (uart_circ_empty(xmit))
			break;
	} while (--count > 0);
    /* printk("(%d bytes)\n", port->icount.tx-cnt); */
	if(uart_circ_chars_pending(xmit) < WAKEUP_CHARS) {
		spin_unlock(&port->lock);
		uart_write_wakeup(port);
		spin_lock(&port->lock);
	}
	if(uart_circ_empty(xmit))
		ch438_stop_tx(port);
}

static unsigned char ch438_modem_status(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);
	unsigned int status = UART_GET_MSR(port);
	
	status |= up->msr_saved_flags;
	up->msr_saved_flags = 0;

	if(status & BIT_MSR_ANY_DELTA && up->ier & BIT_IER_MSI && 
		port->state != NULL) {

		if (status & BIT_MSR_TERI)
			port->icount.rng++;
		
		if (status & BIT_MSR_DDSR)
			port->icount.dsr++;

		if (status & BIT_MSR_DDCD)
			uart_handle_dcd_change(port, status & BIT_MSR_DCD);

		if (!(up->mcr & BIT_MCR_AFE) && status & BIT_MSR_DCTS)
			uart_handle_cts_change(port, status & BIT_MSR_CTS);

		wake_up_interruptible(&port->state->port.delta_msr_wait);
	}

	SERIAL_DBG("ttySC%d modem status: %x\n", port->line, status);

	return status;
}

static void irq_todo(struct uart_port *port)
{
	unsigned long flags=0;
    unsigned char iir = UART_GET_IIR(port) & BIT_IIR_IID_MASK;
	unsigned char lsr = UART_GET_LSR(port);
	
    spin_lock_irqsave(&port->lock, flags);

	SERIAL_DBG("ttySC%d irq: iir=0x%02x, lsr=0x%02x, ier=0x%02x\n", 
	    port->line, iir, lsr, UART_GET_IER(port));
   
    if(!(lsr & (BIT_LSR_BI | BIT_LSR_FE | BIT_LSR_PE | BIT_LSR_OE))) {
        if(lsr & BIT_LSR_DR)
            ch438_handle_rx(port, lsr);
    } else {
        UART_GET_CHR(port); /* clear error */
    }
    ch438_modem_status(port);

    if(lsr & BIT_LSR_THRE) {
        ch438_handle_tx(port);
    }

	spin_unlock_irqrestore(&port->lock, flags);
}

static irqreturn_t ch438_irq(int irq, void *dev_id)
{
	unsigned char ssr = 0;

    ssr = UART_GET_SSR();

    if(ssr & 0x01)
        irq_todo(&ch438_ports[0].port);

    if(ssr & 0x02)
        irq_todo(&ch438_ports[1].port);

    if(ssr & 0x04)
        irq_todo(&ch438_ports[2].port);

    if(ssr & 0x08)
        irq_todo(&ch438_ports[3].port);

    if(ssr & 0x10) 
        irq_todo(&ch438_ports[4].port);

    if(ssr & 0x20)
        irq_todo(&ch438_ports[5].port);

    if(ssr & 0x40)
        irq_todo(&ch438_ports[6].port);

    if(ssr & 0x80)
        irq_todo(&ch438_ports[7].port);

	return IRQ_HANDLED;
}

static inline void wait_for_xmitr(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);
	unsigned int status, tmout = 1000;
	unsigned char mask = BIT_LSR_TEMT|BIT_LSR_THRE;
    SERIAL_DBG("ttySC%d\n", port->line);
	/* Wait up to 1ms for the character to be sent. */
	do {
		status = UART_GET_LSR(port);

		if(status & BIT_LSR_BI)
			up->lsr_break_flag = BIT_LSR_BI;

		if (--tmout == 0)
			break;
		udelay(1);
	} while ((status & mask) != mask);

	/* CTS is unsupported by the 2-line UART, so ignore it. */
	if(up->io_num == 2)
		return;

	/* Wait up to 1s for flow control if necessary */
	if (port->flags & UPF_CONS_FLOW) {
		tmout = 1000000;
		while(--tmout && (UART_GET_MSR(port)&BIT_MSR_CTS)==0) {
			udelay(1);
		}
	}
}

static unsigned int ch438_tx_empty(struct uart_port *port)
{
	unsigned long flags = 0;
	unsigned int ret = 0;
	
	spin_lock_irqsave(&port->lock, flags);
	ret = (UART_GET_LSR(port) & BIT_LSR_TEMT) ? TIOCSER_TEMT : 0;
	spin_unlock_irqrestore(&port->lock, flags);

	return ret;
}

static void ch438_set_mctrl(struct uart_port *port, u_int mctrl)
{
	struct ch438_port *up = UART_TO_SPORT(port);
	unsigned int mcr=0;

	/* ch438q not used RTS & DTR */
	if(mctrl & TIOCM_LOOP) 
		mcr |= BIT_MCR_LOOP;
	if(mctrl & TIOCM_OUT2)
		mcr |= BIT_MCR_OUT2;
	up->mcr &= ~(BIT_MCR_RTS|BIT_MCR_DTR|BIT_MCR_LOOP|BIT_MCR_OUT2);
	up->mcr |= mcr; 
	SERIAL_DBG("ttySC%d set mcr 0x%02X\n", port->line, mcr);
	UART_PUT_MCR(port, up->mcr);
}

static unsigned int ch438_get_mctrl(struct uart_port *port)
{
	unsigned int msr;
	unsigned int ret = 0;

	msr = ch438_modem_status(port);
	if (msr & BIT_MSR_DCD) 
		ret |= TIOCM_CAR;
	if (msr & BIT_MSR_RI)  
		ret |= TIOCM_RNG;
	if (msr & BIT_MSR_DSR) 
		ret |= TIOCM_DSR;
	if (msr & BIT_MSR_CTS) 
		ret |= TIOCM_CTS;
	SERIAL_DBG("ttySC%d get msr 0x%02X\n", port->line, msr);
	return ret;
}

static void ch438_stop_rx(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);

	if (up->ier & BIT_IER_RLSI) {
		up->ier &= ~BIT_IER_RLSI;
		SERIAL_DBG("ttySC%d stop rx, ier 0x%02X\n", port->line, up->ier);
		port->read_status_mask &= ~BIT_LSR_DR;
		UART_PUT_IER(port, up->ier);
	}
}

static void ch438_enable_ms(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);

	if(!(up->ier & BIT_IER_MSI)) {
		up->ier |= BIT_IER_MSI;
		SERIAL_DBG("ttySC%d en msi, ier 0x%02X\n", port->line, up->ier);
		UART_PUT_IER(port, up->ier);
	}
}

static void ch438_break_ctl(struct uart_port *port, int break_state)
{
	struct ch438_port *up = UART_TO_SPORT(port);
	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);
	if (break_state == -1)
		up->lcr |= BIT_LCR_SBC;
	else
		up->lcr &= ~BIT_LCR_SBC;
	UART_PUT_LCR(port, up->lcr);
	spin_unlock_irqrestore(&port->lock, flags);
}

static int ch438_startup(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);

	SERIAL_DBG("ttySC%d start up ...\n", port->line);

	up->msr_saved_flags = 0;
	up->ier = BIT_IER_RLSI |BIT_IER_THRI | BIT_IER_RDI;
	UART_PUT_IER(port, up->ier); /* Interrupt when starting */
	return 0;
}

static void ch438_shutdown(struct uart_port *port)
{
	struct ch438_port *up = UART_TO_SPORT(port);

	SERIAL_DBG("ttySC%d shut down ...\n", port->line);
	up->lcr = 0;
	up->mcr = 0;
	up->fcr = 0;
	up->ier &= ~(BIT_IER_RLSI|BIT_IER_THRI|BIT_IER_RDI);
	/* the value of the register must be modified! */
	UART_PUT_IER(port, up->ier); 
}

static void ch438_set_termios(struct uart_port *port, struct ktermios *termios, 
				struct ktermios *old)
{
	struct ch438_port *up = UART_TO_SPORT(port);
	unsigned long flags;
	unsigned int baud, quot, lcr = 0, dll, dlh;

	if(1) {
		/* define custom termios settings for NMEA protocol */
		termios->c_iflag /* input modes - */
			&= ~(IGNBRK  /* disable ignore break */
			| BRKINT     /* disable break causes interrupt */
			| PARMRK     /* disable mark parity errors */
			| ISTRIP     /* disable clear high bit of input char */
			| INLCR      /* disable translate NL to CR */
			| IGNCR      /* disable ignore CR */
			| ICRNL      /* disable translate CR to NL */
			| IXON);     /* disable enable XON/XOFF flow control */

		termios->c_oflag /* output modes */
			&= ~OPOST;    /* disable postprocess output char */

		termios->c_lflag /* line discipline modes */
			&= ~(ECHO     /* disable echo input characters */
			| ECHONL      /* disable echo new line */
			| ICANON      /* disable erase, kill, werase, and rprnt
					 special characters */
			| ISIG        /* disable interrupt, quit, and suspend
					 special characters */
			| IEXTEN);    /* disable non-POSIX special characters */
	} /* CT_CYPHIDCOM: Application should handle this for device */

	switch (termios->c_cflag & CSIZE) {
		case CS5: 
			lcr = BIT_LCR_WLEN_5; 
			break;
		case CS6: 
			lcr = BIT_LCR_WLEN_6; 
			break;
		case CS7: 
			lcr = BIT_LCR_WLEN_7; 
			break;
		case CS8:
		default:  
			lcr = BIT_LCR_WLEN_8; 
			break;
	}

	if (termios->c_cflag & CSTOPB)
		lcr |= BIT_LCR_STOP;
	if (termios->c_cflag & PARENB)
		lcr |= BIT_LCR_PARITY;
    if (!(termios->c_cflag & PARODD))
        lcr |= BIT_LCR_EPAR;

	/* set baudrate */
	baud = uart_get_baud_rate(port, termios, old, 
					port->uartclk / 16 / 0xffff, 
					port->uartclk / 16);
	quot = uart_get_divisor(port, baud);
	dll = quot & 0xFF;
	dlh = quot >> 8;
	/* SERIAL_DBG("baud=%d, quot=%d\n", baud, quot); */

	spin_lock_irqsave(&port->lock, flags);
	/* Update the per-port timeout. */
	uart_update_timeout(port, termios->c_cflag, baud);

	port->read_status_mask = BIT_LSR_OE | BIT_LSR_THRE | BIT_LSR_DR;
	if (termios->c_iflag & INPCK)
		port->read_status_mask |= BIT_LSR_FE | BIT_LSR_PE;
	if (termios->c_iflag & (BRKINT | PARMRK))
		port->read_status_mask |= BIT_LSR_BI;
    
	/* Characters to ignore */
	port->ignore_status_mask = 0;
	if (termios->c_iflag & IGNPAR)
		port->ignore_status_mask |= BIT_LSR_PE | BIT_LSR_FE;
	if (termios->c_iflag & IGNBRK) {
		port->ignore_status_mask |= BIT_LSR_BI;
		/*
		 * If we're ignoring parity and break indicators,
		 * ignore overruns too (for real raw support).
		 */
		if (termios->c_iflag & IGNPAR)
			port->ignore_status_mask |= BIT_LSR_OE;
	}

	/*
	 * Ignore all characters if CREAD is not set.
	 */
	if ((termios->c_cflag & CREAD) == 0)
		port->ignore_status_mask |= BIT_LSR_DR;

	/* 
	 * if lcr & baud are changed, reset controller to disable transfer 
	 */
	if(lcr != up->lcr || dll != up->dll || dlh != up->dlh) {
		/* SERIAL_DBG("ttySC%d reset controller...\n", port->line); */
		ch438_reset(port);
	}
	up->dll = dll;
	up->dlh = dlh;

	/* flow control */
	up->mcr &= ~ BIT_MCR_AFE;
	if(termios->c_cflag & CRTSCTS)
		up->mcr |= BIT_MCR_AFE;
	UART_PUT_MCR(port, up->mcr);

	/* 
	 * CTS flow control flag and modem status interrupts 
	 */
	up->ier &= ~BIT_IER_MSI;
	if(UART_ENABLE_MS(port, termios->c_cflag))
		up->ier |= BIT_IER_MSI;
	UART_PUT_IER(port, up->ier);

	up->fcr = BIT_FCR_FT_112|BIT_FCR_FIFOEN;
	UART_PUT_FCR(port, up->fcr);

	up->lcr = lcr;
	UART_PUT_LCR(port, up->lcr|BIT_LCR_DLAB); /* set DLAB */
	UART_PUT_DLL(port, up->dll);
	UART_PUT_DLH(port, up->dlh);
	UART_PUT_LCR(port, up->lcr); /* reset DLAB */

	/* Don't rewrite B0 */
	if(tty_termios_baud_rate(termios))
		tty_termios_encode_baud_rate(termios, baud, baud);

	ch438_set_mctrl(port, port->mctrl);
    spin_unlock_irqrestore(&port->lock, flags);
}

static const char *ch438_type(struct uart_port *port)
{
	return "CH438";
}

static void ch438_release_port(struct uart_port *port)
{
}

/*
 * Request the memory region(s) being used by 'port'
 */
static int ch438_request_port(struct uart_port *port)
{
    return 0;
}

/*
 * Configure/autoconfigure the port.
 */
static void ch438_config_port(struct uart_port *port, int flags)
{
    if(flags & UART_CONFIG_TYPE)
    {
        port->type = PORT_CH438;
        ch438_request_port(port);
    }
}

/*
 * verify the new serial_struct (for TIOCSSERIAL).
 */
static int ch438_verify_port(struct uart_port *port, struct serial_struct *ser)
{
	/* We don't want the core code to modify any port params */
    return -EINVAL;
}

static void ch438_pm(struct uart_port *port, unsigned int state, 
						unsigned int oldstate)
{
	struct ch438_port *up = UART_TO_SPORT(port);

	switch(state) {
	case 0: /* Power up */
	    up->ier &= ~BIT_IER_LOWPWR;
		UART_PUT_IER(port, up->ier);
		SERIAL_DBG("ttySC%d PM state:%d --ON--\n", port->line, state);
		break;
	case 3: /* Power down */
	    up->ier |= BIT_IER_LOWPWR;
		UART_PUT_IER(port, up->ier);
		SERIAL_DBG("ttySC%d PM state:%d --OFF--\n", port->line, state);
		break;
	default:
		SERIAL_DBG("usart%d, Unknown PM state %d\n", up->id, state);
	}
}

static struct uart_ops ch438_uart_ops = {
	.tx_empty		= ch438_tx_empty,
	.set_mctrl		= ch438_set_mctrl,
	.get_mctrl		= ch438_get_mctrl,
	.stop_tx		= ch438_stop_tx,
	.start_tx		= ch438_start_tx,
	.stop_rx		= ch438_stop_rx,
	.enable_ms		= ch438_enable_ms,
	.break_ctl		= ch438_break_ctl,
	.startup		= ch438_startup,
	.shutdown		= ch438_shutdown,
	.set_termios	= ch438_set_termios,
	.type			= ch438_type,
	.release_port	= ch438_release_port,
	.request_port	= ch438_request_port,
	.config_port	= ch438_config_port,
	.verify_port	= ch438_verify_port,
	.pm             = ch438_pm,
};

static ssize_t ch438_dev_info_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct uart_port *port = dev_get_drvdata(dev);
	struct ch438_port *up = UART_TO_SPORT(port);
	
	return snprintf(buf, PAGE_SIZE,
		"id     = %d \n"
	   	"name   = %s \n"
		"irq    = %d \n"
	   	"io_num = %d \n"
		"port->membase = 0x%08x \n"
		"port->iobase  = 0x%08x \n",
		up->id, up->name, port->irq, 2,
		(unsigned int)port->membase,	
		(unsigned int)port->iobase);
}

static struct device_attribute ch438_dev_info_attr = 
	__ATTR(dev_info, S_IRUGO, ch438_dev_info_show, NULL);

static ssize_t ch438_status_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct uart_port *port = dev_get_drvdata(dev);

	return snprintf(buf, PAGE_SIZE, 
		"uartclk = %d \n"
	   	"RBR = 0x%02X, IER = 0x%02X, IIR = 0x%02X \n"
	   	"FCR = 0x%02X, LCR = 0x%02X, MCR = 0x%02X \n"
	   	"LSR = 0x%02X, MSR = 0x%02X, SCR = 0x%02X \n",
	   	port->uartclk,
		UART_GET_CHR(port),
		UART_GET_IER(port),
		UART_GET_IIR(port),
		UART_GET_FCR(port),
		UART_GET_LCR(port),
		UART_GET_MCR(port),
		UART_GET_LSR(port),
		UART_GET_MSR(port),
		UART_GET_SCR(port));
}

static struct device_attribute ch438_status_attr =
	__ATTR(status, S_IRUGO, ch438_status_show, NULL);

static ssize_t ch438_loopback_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int mcr = 0;
	struct uart_port *port = dev_get_drvdata(dev);

	mcr = UART_GET_MCR(port);

	return snprintf(buf, PAGE_SIZE,
	   	"MCR: 0x%08x, Loopback: %d\n", mcr, mcr&BIT_MCR_LOOP ? 1 : 0);
}

static ssize_t ch438_loopback_store(struct device *dev,
				  struct device_attribute *attr,
				  const char *buf, size_t count)
{
	int mcr = 0;
	int enable = 0;
	struct uart_port *port = dev_get_drvdata(dev);

	if (!strncmp(buf, "enable", 6))
		enable = 1;
	
	SERIAL_DBG("Set loopback: %d \n", enable);

	mcr = UART_GET_MCR(port);
	if (enable)
		UART_PUT_MCR(port, mcr | BIT_MCR_LOOP);
	else
		UART_PUT_MCR(port, mcr & ~BIT_MCR_LOOP);
	
	return count;
}
static struct device_attribute ch438_loopback_attr =
	__ATTR(loopback, S_IRUGO|S_IWUSR, ch438_loopback_show, ch438_loopback_store);

static ssize_t ch438_ctrl_info_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct uart_port *port = dev_get_drvdata(dev);
	struct ch438_port *up = UART_TO_SPORT(port);
	u32 dl = (u32)up->dlh << 8 | (u32)up->dll;

	if(dl == 0) 
		dl = 1000;

	return snprintf(buf, PAGE_SIZE, 
		"ier : 0x%02X\n"
		"lcr : 0x%02X\n"
		"mcr : 0x%02X\n"
		"fcr : 0x%02X\n"
		"dll : 0x%02X\n"
		"dlh : 0x%02X\n"
		"baud: %d (dl = %d)\n\n"
		"TxRx Statistics:\n"
		"tx     : %d\n"
		"rx     : %d\n"
		"parity : %d\n"
		"frame  : %d\n"
		"overrun: %d\n",
		up->ier, up->lcr, up->mcr, 
		up->fcr, up->dll, up->dlh,
		(port->uartclk>>4)/dl, dl, 
		port->icount.tx,
		port->icount.rx,
		port->icount.parity,
		port->icount.frame,
		port->icount.overrun);
}
static struct device_attribute ch438_ctrl_info_attr =
	__ATTR(ctrl_info, S_IRUGO, ch438_ctrl_info_show, NULL);

static void ch438_sysfs(struct platform_device *pdev)
{
	device_create_file(&pdev->dev, &ch438_dev_info_attr);
	device_create_file(&pdev->dev, &ch438_status_attr);
	device_create_file(&pdev->dev, &ch438_loopback_attr);
	device_create_file(&pdev->dev, &ch438_ctrl_info_attr);
}

static void __init ch328_device_scan(void)
{
	struct uart_port *port;
	int i;
	
	memset(ch438_device, 0, sizeof(ch438_device));
	memset(ch438_ports,  0, sizeof(ch438_ports));

	for(i=0; i<CH438_DEV_NUM; i++) {
		port = &ch438_ports[i].port;

		ch438_device[i].name = CH438_UART_NAME;
		ch438_device[i].id   = i;
		ch438_device[i].dev.platform_data = port;

		port->iotype   = UPIO_PORT;
		port->ops      = &ch438_uart_ops;
		port->fifosize = 128; /* max: 128 */
		port->line     = i;
	}
}

static struct uart_driver ch438_uart_driver = {
	.owner			= THIS_MODULE,
	.driver_name	= CH438_UART_NAME,
	.dev_name		= CH438_DEV_NAME,
	.nr				= CH438_DEV_NUM,
	.cons			= NULL,
};

struct platform_device *ch438_get_pdev(int uart_id)
{
	if(ch438_ports[uart_id].port.dev)
		return to_platform_device(ch438_ports[uart_id].port.dev);
	else 
		return NULL;
}
EXPORT_SYMBOL(ch438_get_pdev);

static int __devinit ch438_probe(struct platform_device *pdev)
{
	struct uart_port *port;
	struct ch438_port *up;
	struct ch438_pin *pin = &ch438_pin;
	int id = pdev->id;
	int ret;

	port = &ch438_ports[pdev->id].port;
	port->dev = &pdev->dev;
	up = UART_TO_SPORT(port);
	up->id = id;
	up->ier = 0;
	up->lcr = 0;
	up->mcr = 0;
	up->fcr = 0;
	up->dll = 0;
	up->dlh = 0;
	up->io_num = 2;
	sprintf(up->name, CH438_UART_NAME"%d", id);
	pdev->dev.init_name = up->name;

	port->uartclk = CH438_CLOCK_RATE;

	port->type = PORT_CH438;
	port->flags = UPF_BOOT_AUTOCONF;
	port->mapbase = 0; /* it was not used! */
	port->irq = pin->irqnum;
	port->mctrl = TIOCM_OUT2; /* use BIT_MCR_OUT2 generate an interrupt */
	platform_set_drvdata(pdev, port);
	
	ret = request_irq(port->irq, ch438_irq, 
            IRQF_TRIGGER_FALLING|IRQF_DISABLED|IRQF_SHARED, up->name, port);
	if(unlikely(ret)) {
		SERIAL_DBG("usart%d cannot get irq%d\n", up->id, port->irq);
	}

	uart_add_one_port(&ch438_uart_driver, port);

    return 0;
}

static int __devexit ch438_remove(struct platform_device *pdev)
{
	struct uart_port *port = platform_get_drvdata(pdev);
	SERIAL_DBG("release uart%d port\n", port->line);
	
	free_irq(port->irq, port);

	return 0;
}

/* UART power management code */
#ifdef CONFIG_PM_SLEEP
static int ch438_suspend(struct device *dev)
{
	struct uart_port *port = dev_get_drvdata(dev);

	if (port) {
		SERIAL_DBG("usart%d suspend.\n", port->line);
		uart_suspend_port(&ch438_uart_driver, port);
	}

	return 0;
}

static int ch438_resume(struct device *dev)
{
	struct uart_port *port = dev_get_drvdata(dev);

	if (port) {
		uart_resume_port(&ch438_uart_driver, port);
		SERIAL_DBG("usart%d resume.\n", port->line);
	}

	return 0;
}

static const struct dev_pm_ops ch438_pm_ops = {
	.suspend = ch438_suspend,
	.resume = ch438_resume,
};
#define CH438_PM_OPS	(&ch438_pm_ops)
#else /* !CONFIG_PM_SLEEP */
#define CH438_PM_OPS	NULL
#endif /* CONFIG_PM_SLEEP */

static struct platform_driver ch438_platform_driver =
{
	.probe  = ch438_probe,
	.remove = ch438_remove,
	.driver = {
		.name  = CH438_UART_NAME,
		.pm    = CH438_PM_OPS,
		.owner = THIS_MODULE,
	},
};

static int __init ch438_uart_init(void)
{
	int i;

    spin_lock_init(&ch438_rwlock);
	SERIAL_DBG("ch438 uart init\n");
	ch438_mapbase = CH438_BASE;
	ch438_membase = ioremap(ch438_mapbase, GPIO_RANGE);
    if(!ch438_membase) {
        SERIAL_DBG("ch438, ioremap failed\n");
        release_mem_region(ch438_mapbase, GPIO_RANGE);
    }
    
    ch438_gpio_init();
	ch328_device_scan();
    
	/* SERIAL_DBG("register ch438 driver...\n"); */
	uart_register_driver(&ch438_uart_driver);

	/* SERIAL_DBG("register platform_device...\n"); */
	for(i=0; i<CH438_DEV_NUM; i++) {
		platform_device_register(&ch438_device[i]);
		ch438_sysfs(&ch438_device[i]);
	}

	return platform_driver_register(&ch438_platform_driver);
}

static void __exit ch438_uart_exit(void)
{
    release_mem_region(ch438_mapbase, GPIO_RANGE);
    iounmap(ch438_membase);
    ch438_membase = NULL;
	platform_driver_unregister(&ch438_platform_driver);
	uart_unregister_driver(&ch438_uart_driver);
	ch438_gpio_free();
}

module_init(ch438_uart_init);
module_exit(ch438_uart_exit);

MODULE_DESCRIPTION("CH438 serial port driver");
MODULE_AUTHOR("iamyhw@foxmail.com");
MODULE_LICENSE("GPL");